/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package provider.impl.genomemap.data;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * An algorithm to select probes unsuitable for analysis as they violate model conditions. The
 * following model condions are accounted for:
 * <ol>
 * <li>
 * Probes should hybridize to themselves
 * </li>
 * <li>
 * No duplicate probes (identity of a probe is detrmined by its name).
 * </li>
 * <li>
 * Probes should be non-overlapping among themselves in terms of hybridization score. In choosing
 * a single probe among the overlapping ones we follow the algorithm below:
 * <ol>
 * <li>
 * Use the probe that has most number of genes assigned to it.
 * </li>
 * <li>
 * If there is a tie, prefer probe that starts with "H" over "X" as the hybridization
 * is cleaner in that case.
 * </li>
 * <li>
 * If still there is a tie, prefer the first among the ties.
 * </li>
 * </ol>
 * </li>
 * </ol>
 * The algorithm should be deterministic and hence should select the same probes when repeated with
 * the same data. The correctness of some implementations of InputData may depend on this.
 *
 * @author Susanta Tewari
 * @version 1.0 Dec 29, 2010
 */
abstract class ProbesPruningAlgo {

    /**
     * Selects the probes violating model assumptions and thus becoming unsuitable for analysis. The
     * following model assumption on the probes are checked.
     * <ol>
     * <li>
     * Probes should hybridize to themselves
     * </li>
     * <li>
     * No duplicate probes (identity of a probe is detrmined by its name).
     * </li>
     * <li>
     * Probes should be non-overlapping among themselves in terms of hybridization score. In choosing
     * a single probe among the overlapping ones we follow the algorithm below:
     * <ol>
     * <li>
     * Use the probe that has most number of genes assigned to it.
     * </li>
     * <li>
     * If there is a tie, prefer probe that starts with "H" over "X" as the hybridization
     * is cleaner in that case.
     * </li>
     * <li>
     * If still there is a tie, prefer the first among the ties.
     * </li>
     * </ol>
     * </li>
     * </ol>
     *
     * @param data hybridization data
     * @param clones clones in <code>data</code>
     * @param probes probes in  <code>data</code>
     * @return indices of probes to be pruned
     * @todo specification is not fully implemented
     */
    abstract Set<Integer> removedIndices(byte[][] data, List<String> clones, List<String> probes);

    static ProbesPruningAlgo getDefaultAlgo() {

        return new ProbesPruningAlgo() {

            @Override
            Set<Integer> removedIndices(final byte[][] data, final List<String> clones,
                                        final List<String> probes) {

                final Set<Integer> removal_set            = new HashSet<>();
                final Set<Integer> probes_not_in_clones   = new HashSet<>();
                final Set<Integer> probes_not_self_hybrid = new HashSet<>();
                final Set<Integer> probes_overlapping     = new HashSet<>();
                final Set<Integer> probes_duplicate       = new HashSet<>();


                // duplicate fullProbes
                final List<String> probes_list  = new ArrayList<>(probes);
                final List<String> growing_list = new ArrayList<>();

                for (int i = 0; i < probes_list.size(); i++) {

                    final String probe = probes_list.get(i);

                    growing_list.add(probe);


                    // duplicate fullProbes
                    if (Collections.frequency(growing_list, probe) > 1) {

                        removal_set.add(i);
                        probes_duplicate.add(i);
                    }


                    // fullProbes not in clones
                    final int index = clones.indexOf(probe);

                    if (index == -1) {

                        removal_set.add(i);
                        probes_not_in_clones.add(i);

                    }    // probe that does not self hybridize
                            else if (data[index][i] != 1) {

                        removal_set.add(i);
                        probes_not_self_hybrid.add(i);
                    }
                }

                for (int i = 0; i < probes.size(); i++) {

                    if (!removal_set.contains(i)) {

                        final int rowIndex = clones.indexOf(probes.get(i));

                        for (int j = 0; j < probes.size(); j++) {

                            if (!removal_set.contains(j)) {

                                if ((i != j) && (data[rowIndex][j] != 0)) {

                                    removal_set.add(j);
                                    probes_overlapping.add(j);
                                }
                            }
                        }
                    }
                }

                Logger.getLogger(this.getClass().getName()).log(Level.INFO,
                                 "Probes duplicate \n count: " + probes_duplicate.size()
                                 + "\n indices: " + probes_duplicate + "\n names: "
                                 + findNames(probes, probes_duplicate));
                Logger.getLogger(this.getClass().getName()).log(Level.INFO,
                                 "Probes not present in clones \n count: "
                                 + probes_not_in_clones.size() + "\n indices: "
                                 + probes_not_in_clones + "\n names: "
                                 + findNames(probes, probes_not_in_clones));
                Logger.getLogger(this.getClass().getName()).log(Level.INFO,
                                 "Probes not self hybridizing \n count: "
                                 + probes_not_self_hybrid.size() + "\n indices: "
                                 + probes_not_self_hybrid + "\n names: "
                                 + findNames(probes, probes_not_self_hybrid));
                Logger.getLogger(this.getClass().getName()).log(Level.INFO,
                                 "Probes overlapping \n count: " + probes_overlapping.size()
                                 + "\n indices: " + probes_overlapping + "\n names: "
                                 + findNames(probes, probes_overlapping));

                return removal_set;
            }
            private Set<String> findNames(final List<String> names, final Set<Integer> indices) {

                final Set<String> result = new HashSet<>();

                for (final Integer index : indices) {
                    result.add(names.get(index));
                }

                return result;
            }
        };
    }
}
